#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <libgen.h>
#include "bl_crc32.h"
#include "package.h"

#define MODEL_MAX_LEN   8
#define PARTN_MAX_LEN   12

char *outfile = "update.lub"; /* 输出文件的文件名 */

typedef struct {
    uint32_t magic_numb;
    uint32_t tbl_ver;        // [31:24] major, [23:12] minor [11:0] reversion
    char model[MODEL_MAX_LEN];
    uint16_t flag;
    uint16_t block_size;
    uint32_t build_ts;
    uint32_t ctx_crc;
    uint32_t raw_size;
    uint32_t ctx_size;
    char parti_name[PARTN_MAX_LEN];
    char reserve1[16];
    char reserve2[12];
    uint32_t header_crc;
}bl_header_info, *bl_header_info_t;

typedef struct _ddr2buf
{
    unsigned int addr;
    unsigned int val;
}DDR2Buf;

typedef struct _spi_flash_header
{
    unsigned int ftyp;
    unsigned int exec_addr;
    unsigned int app_sz;
    unsigned int reserved1;
    unsigned int AA55;
    unsigned int ddr2_sz;
    DDR2Buf buf[1];   // ddr2_sz + 1
}AppHeader;

AppHeader defHeader = {
    .ftyp = 0x4E565420,
    .exec_addr = 0,
    .app_sz = 0,
    .reserved1 = 0xFFFFFFFF,
    .AA55= 0xAA55AA55,
    .ddr2_sz = 54,
    .buf = {
    {0x0, 0x0},
    },
};

bool verbose = false;

AppHeader *appHeader = NULL;
DDR2Buf *ddr2_array = NULL;
int ddr2_array_sz = 8;
int ddr2_sz = 0;

static void printUsage(const char *appname)
{
    char *name, *bname;
    name = strdup(appname);
    bname = basename(name);
    printf( "Version: " SW_STR_VERSION "\r\n"
            "Usage: %s OPTIONS\r\n"
            "Make lub from bin\r\n"
            "\r\n"
            "OPTIONS:\r\n"
            "    --model || -m <instrument model>       8 chars max\r\n"
            "    --partition || -p <flash partition>    12 chars max\r\n"
            "    --ddr || -d <ddr ini file>             DDR Ini file\r\n"
            "    -i <src bin>                           Input bin file\r\n"
            "    [-o <out path>]                        Output file, <update.lub> default\r\n"
            "    -v || --verbose                        Enable verbose output\r\n"
            , bname);
    free(name);
}

static int loadDDR2Init(const char *ddr2file)
{
    FILE *ddr2fd;
    struct stat st;
    char buf[64], cvt[32], *psep;
    DDR2Buf *pddr2 = ddr2_array;

    if(stat(ddr2file, &st) < 0){
        perror("Stat file ");
        return -1;
    }

    ddr2fd = fopen(ddr2file, "r");
    if(!ddr2fd){
        printf("Open DDR initial file %s error!\n", ddr2file);
        return -1;
    }

    while (!feof(ddr2fd)) {
        fgets(buf, 64, ddr2fd);
        psep = strchr(buf, '=');
        if (psep == NULL) {
            printf("DDR initial format error\n");
            fclose(ddr2fd);
            return -1;
        } else {
            if (ddr2_sz == ddr2_array_sz) {
                ddr2_array_sz += 8;
                ddr2_array = realloc(ddr2_array, ddr2_array_sz * sizeof(DDR2Buf));
                pddr2 = ddr2_array + ddr2_sz;
            }
            strncpy(cvt, buf, psep - buf);
            cvt[psep - buf] = '\0';
            pddr2->addr = strtoul(cvt, NULL, 0);

            psep++;
            strncpy(cvt, psep, strlen(psep));
            cvt[strlen(psep)] = '\0';
            pddr2->val = strtoul(cvt, NULL, 0);
            if (verbose == true) {
                printf("  DDR2: 0x%08X=0x%08X\n", pddr2->addr, pddr2->val);
            }

            pddr2++;
            ddr2_sz++;
        }
    }

    fclose(ddr2fd);

    return 0;
}

int lube_archive(int argc, char *argv[])
{
    int arg_c = argc-1;
    char **arg_v = &argv[1];
    char *model = NULL, *partition = NULL, *ifile = NULL, *ofile = NULL, *ddrfile = NULL;

    if(argc < 2){
        printUsage(argv[0]);
        return 0;
    }

    while(arg_c > 0){
        if(strcmp(arg_v[0], "--model") == 0 || strcmp(arg_v[0], "-m") == 0){
            model = arg_v[1];
        }else if(strcmp(arg_v[0], "--partition") == 0 || strcmp(arg_v[0], "-p") == 0){
            partition = arg_v[1];
        }else if(strcmp(arg_v[0], "--ddr") == 0 || strcmp(arg_v[0], "-d") == 0){
            ddrfile = arg_v[1];
        }else if(strcmp(arg_v[0], "-i") == 0){
            ifile = arg_v[1];
        }else if(strcmp(arg_v[0], "-o") == 0){
            ofile = arg_v[1];
        }else if(strcmp(arg_v[0], "--verbose") == 0 || strcmp(arg_v[0], "-v") == 0){
            verbose = true;
        }else{
        }
        arg_c--; arg_v++;
    }
    if (((model == NULL) || (strlen(model) > MODEL_MAX_LEN)) ||
        ((partition == NULL) || (strlen(partition) > PARTN_MAX_LEN)) ||
        (ifile == NULL) || ddrfile == NULL) {
        printUsage(argv[0]);
        return 0;
    }

    if (ofile != NULL) {
        outfile = ofile;
    }

    if (verbose == true) {
        printf("Model: %s\n", model);
        printf("Partition: %s\n", partition);
        printf("Bin file: %s\n", ifile);
        printf("LUB: %s\n", ofile);
        printf("LUB Version: %s\n", SW_STR_VERSION);
        printf("\n");
    }

    ddr2_array = malloc(ddr2_array_sz * sizeof(DDR2Buf));
    if (loadDDR2Init(ddrfile) < 0) {
        return 0;
    }

    FILE *ifp, *ofp;
    struct stat st;
    bl_header_info header;
    size_t raw_size = 0;
    size_t ctx_size = 0;
    uint8_t *ctx_buf = NULL;
    size_t rd;

    if(stat(ifile, &st) < 0){
        perror("Stat file ");
        return -1;
    }
    raw_size = st.st_size;
    ifp = fopen(ifile, "rb");
    if(!ifp){
        printf("Open file %s failure!\n", ifile);
        return -1;
    }
    ofp = fopen(outfile, "wb+");
    if(!ofp){
        printf("Open file %s failure!\n", outfile);
        fclose(ifp);
        return -1;
    }

    header.magic_numb = IDENTIFIER;
    header.tbl_ver = SOFTWARE_VERSION;
    strncpy(header.model, model, MODEL_MAX_LEN);
    header.block_size = BLOCK_SIZE;
    strncpy(header.parti_name, partition, PARTN_MAX_LEN);

    //time(&tim);
    header.build_ts = st.st_ctime;//tim;
    header.raw_size = raw_size;
    defHeader.app_sz = raw_size;
    defHeader.ddr2_sz = ddr2_sz;

    appHeader = (AppHeader*)malloc(sizeof(AppHeader) + defHeader.ddr2_sz * sizeof(DDR2Buf));
    memset(appHeader, 0, sizeof(AppHeader) + defHeader.ddr2_sz * sizeof(DDR2Buf));
    memcpy(appHeader, &defHeader, sizeof(AppHeader));
    memcpy(appHeader->buf, ddr2_array, defHeader.ddr2_sz * sizeof(DDR2Buf));

    ctx_buf = malloc(4 * 1024 * 1024);
    memcpy(ctx_buf, appHeader, sizeof(AppHeader) + defHeader.ddr2_sz * sizeof(DDR2Buf));
    ctx_size = sizeof(AppHeader) + defHeader.ddr2_sz * sizeof(DDR2Buf);

    while (1) {
        rd = fread(&ctx_buf[ctx_size], 1, header.block_size, ifp);
        if (rd <= 0) break;
        ctx_size += rd;
    }

    // context crc & size
    header.ctx_crc = bl_crc32sum(ctx_buf, ctx_size);
    header.ctx_size = ctx_size;

    // header crc
    header.header_crc = bl_crc32sum((uint8_t*)&header, sizeof(bl_header_info)-4);

    if (verbose == true) {
        printf("\nHeader { ");
        printf("Version: %s, ", SW_STR_VERSION);
        printf("Model: %s, ", header.model);
        printf("Partition: %s, ", header.parti_name);
        printf("Block Size: %d, ", header.block_size);
        printf("Raw Size: %08X, ", header.raw_size);
        printf("Ctx Size: %08X, ", header.ctx_size);
        printf("Ctx CRC: %08X, ", header.ctx_crc);
        printf("Header CRC: %08X ", header.header_crc);
        printf("}\n");
    } else {
        printf("raw size: %08X %d\n", raw_size, raw_size);
        printf("ctx size: %08X %d\n", ctx_size, ctx_size);
        printf("ctx  crc: %08X\n", header.ctx_crc);
        printf("hdr  crc: %08X\n", header.header_crc);
    }
    // write out file
    fwrite(&header, sizeof(bl_header_info), 1, ofp);
    fwrite(ctx_buf, ctx_size, 1, ofp);

_err_exit:
    if (appHeader) free(appHeader);
    if (ddr2_array) free(ddr2_array);
    if (ctx_buf) free(ctx_buf);

    fclose(ofp);
    fclose(ifp);

    return 0;
}
